/*
 * Copyright 2003-2006 (c) Live Media Pty Ltd. <argot@einet.com.au> 
 *
 * This software is licensed under the Argot Public License 
 * which may be found in the file LICENSE distributed 
 * with this software.
 *
 * More information about this license can be found at
 * http://www.einet.com.au/License
 * 
 * The Developer of this software is Live Media Pty Ltd,
 * PO Box 4591, Melbourne 3001, Australia.  The license is subject 
 * to the law of Victoria, Australia, and subject to exclusive 
 * jurisdiction of the Victorian courts.
 */

%module(package = "argot") type

%pythoncode
%{
#
# Copyright 2003-2006 (c) Live Media Pty Ltd. <argot@einet.com.au> 
#
# This software is licensed under the Argot Public License 
# which may be found in the file LICENSE distributed 
# with this software.
#
# More information about this license can be found at
# http://www.einet.com.au/License
# 
# The Developer of this software is Live Media Pty Ltd,
# PO Box 4591, Melbourne 3001, Australia.  The license is subject 
# to the law of Victoria, Australia, and subject to exclusive 
# jurisdiction of the Victorian courts.
#
%}

%include "utils.i"
%include python/exception.i


%define %argot_library_callback_code_forward_decl
%{
	int _library_reader_callback( type_input_stream * in, type_element * element, void* data );
	int _library_writer_callback( type_output_stream * out, void * object, type_element * element, void* data );
	int _py_type_library_bind( type_library * library, type_name * name, typeReader reader, PyObject* readerData, typeWriter writer, PyObject* writerData );
	int _py_type_library_register_bind( type_library * library, type_name * name, type_element * structure, typeReader reader, PyObject * readerData, typeWriter writer, PyObject * writerData );
	void _py_type_library_destroy( type_library * library );
%}
%enddef

%define %argot_library_callback_code

	%pythoncode
	%{
	def _library_bind(self, name, reader, writer):
		return _py_type_library_bind( self, name, _library_reader_callback, reader, _library_writer_callback, writer )

	TypeLibrary.bind = _library_bind


	def _library_register_bind(self, name, structure, reader, writer):
		return _py_type_library_register_bind( self, name, structure, _library_reader_callback, reader, _library_writer_callback, writer )

	TypeLibrary.register_bind = _library_register_bind
	%}


	%constant int _library_reader_callback( type_input_stream * in, type_element * element, void* data );
	%constant int _library_writer_callback( type_output_stream * out, void * object, type_element * element, void* data );
	int _py_type_library_bind( type_library * library, type_name * name, typeReader reader, PyObject* readerData, typeWriter writer, PyObject* writerData );
	int _py_type_library_register_bind( type_library * library, type_name * name, type_element * structure, typeReader reader, PyObject * readerData, typeWriter writer, PyObject * writerData );
	void _py_type_library_destroy( type_library * library );

	%{
		void _py_type_library_decref_reader_writer( type_library * library, int id )
		{	
			PyObject* pyReader, *pyWriter;
			type_reader* reader = type_library_get_reader( library, id );
			type_writer* writer = type_library_get_writer( library, id );
			
			if (reader) 
			{
				pyReader = (PyObject*)reader->data;
				Py_XDECREF( pyReader );
			}
			
			if (writer)
			{
				pyWriter = (PyObject*)writer->data;
				Py_XDECREF( pyWriter );
			}
		}

		void _py_type_library_destroy( type_library * library )
		{
			// loop over all definitions in the library and xdecref their readers & writers. otherwise we get mem leaks.
			int i;
			for (i = 0; i < library->count; i++)
			{
				_py_type_library_decref_reader_writer( library, i );
			}
			// todo: -- core dump --
			//type_library_destroy(library);
		}


		void _py_type_library_set_reader_and_writer_data( type_library * library, type_name * name, PyObject* readerData, PyObject* writerData )
		{
			int id = type_library_get_id( library, name );
			
			if (id != TYPE_INVALID_PARAM)
			{
				_py_type_library_decref_reader_writer( library, id );
			}

			Py_XINCREF(readerData);
			Py_XINCREF(writerData);
		}

		
		int _py_type_library_bind( type_library * library, type_name * name, typeReader reader, PyObject* readerData, typeWriter writer, PyObject* writerData )
		{
			_py_type_library_set_reader_and_writer_data(library, name, readerData, writerData);
			
			return type_library_bind( library, name, reader, readerData, writer, writerData );
		}
		
		int _py_type_library_register_bind( type_library * library, type_name * name, type_element * structure, typeReader reader, PyObject * readerData, typeWriter writer, PyObject * writerData )
		{
			_py_type_library_set_reader_and_writer_data(library, name, readerData, writerData);

			return type_library_register_bind( library, name, structure, reader, readerData, writer, writerData );
		}


		int _library_reader_callback( type_input_stream * in, type_element * element, void* data )
		{
			PyObject* callable, *stream, *elem, *args, *res;
			int result;
			
			callable = _validate_callable(data);
			if (!callable) return 0;
			
			stream = SWIG_NewPointerObj( in, SWIGTYPE_p_type_input_stream, 0 );
			elem = SWIG_NewPointerObj( element, SWIGTYPE_p_type_element, 0 );

			args = Py_BuildValue("OO", stream, elem);
			res = PyObject_Call(callable, args, 0);

			Py_DECREF(args);
			Py_XDECREF(stream);
			Py_XDECREF(elem);
			
			result = 0;

			if ( !res ) print_traceback();
			
			if (!res || !PyInt_Check(res))
			{
				printf("You have to return an int in your python reader callback function!\n");
			}
			else
			{
				result = PyInt_AsLong(res);
			}
			
			Py_XDECREF(res);
			
			return result;		
		}
		
		int _library_writer_callback( type_output_stream * out, void * object, type_element * element, void* data )
		{
			PyObject* callable, *stream, *obj, *elem, *args, *res;
			int result = TYPE_OK;

			callable = _validate_callable(data);
			if (!callable) return 0;
			
			stream = SWIG_NewPointerObj( out, SWIGTYPE_p_type_output_stream, 0 );
			//obj = SWIG_NewPointerObj( object, SWIGTYPE_p_void, 0 );
			obj = object;
			elem = SWIG_NewPointerObj( element, SWIGTYPE_p_type_element, 0 );
			
			args = Py_BuildValue("OOO", stream, obj, elem);
			res = PyObject_Call(callable, args, 0);

			Py_DECREF(args);
			Py_XDECREF(stream);
			//Py_XDECREF(obj);
			Py_XDECREF(elem);
			
			if ( !res )
			{
				print_traceback();
				result = NOTYPE;
			}
			
			/*if (!res || !PyInt_Check(res))
			{
				printf("You have to return an int in your python argot_write callback function!\n");
				result = 0;
			}
			else
			{
				result = PyInt_AsLong(res);
			}*/
			
			Py_XDECREF(res);
			
			return result;		
		}
	%}


	%pythoncode
	%{
	def _library_get_reader(self, id):
		result = type_library_get_reader( self, id )
		if result and (result.reader == _library_reader_callback):
			return py_convert_to_python_object( result.data )
		return result

	TypeLibrary.get_reader = _library_get_reader


	def _library_get_writer(self, id):
		result = type_library_get_writer( self, id )
		if result and (result.writer == _library_writer_callback):
			return py_convert_to_python_object( result.data )
		return result

	TypeLibrary.get_writer = _library_get_writer
	%}

	%inline
	%{
		PyObject* py_convert_to_python_object(void* obj)
		{
			return (PyObject*)obj;
		}
	%}

%enddef



%define %type_reader_auto_callback_code
	%inline
	%{
		void * _py_type_reader_auto_callback( type_list * list, type_map * map, void * data )
		{
			PyObject* callable, *pylist, *pymap, *res;
			
			callable = _validate_callable(data);
			if (!callable) return 0;

			pylist = SWIG_NewPointerObj( list, SWIGTYPE_p_type_list, 0 );
			pymap = SWIG_NewPointerObj( map, SWIGTYPE_p_type_map, 0 );

			res = PyObject_CallFunctionObjArgs(callable, pylist, pymap, 0);

			if ( !res ) print_traceback();
				
			//Py_XDECREF(res);
			
			return res;
		}

		int _py_type_reader_auto( type_input_stream * in, type_element * element, PyObject* constructor )
		{
			return type_reader_auto( in, element, _py_type_reader_auto_callback, constructor );
		}
	%}
%enddef



%define %insert_read_callback

	%constant int _py_argot_read_callback( type_input_stream * in, void * data, char * buffer, int size );

	%{
		#ifndef _PY_ARGOT_READ_CALLBACK_DEFINED
		#define _PY_ARGOT_READ_CALLBACK_DEFINED
		
		int _py_argot_read_callback( type_input_stream * in, void * data, char * buffer, int size )
		{
			PyObject* callable, *args, *res;
			char* py_buffer;
			int length;
			
			callable = _validate_callable(data);
			if (!callable) return 0;

			args = Py_BuildValue("(i)", size);
			res = PyObject_Call(callable, args, 0);

			Py_DECREF(args);
			
			if ( !res ) print_traceback();
			
			if (!res || !PyString_Check(res))
			{
				printf("You have to return a string in your python argot_read callback function!\n");
				length = 0;
			}
			else
			{
				PyString_AsStringAndSize(res, &py_buffer, &length);
				//printf("RC: %d %d %d %d %d\n", buffer[0], buffer[1], buffer[2], buffer[3], length);
				assert( size == length );
				memcpy( buffer, py_buffer, sizeof(char) * length );
			}
			
			Py_XDECREF(res);
			
			return length;
		}
		
		#endif
	%}
	
%enddef



%define %to_string_to_void_code
	%inline
	%{
		char* toString(void* ptr)
		{
			return (char*)ptr;
		}

		void* toVoid(PyObject* ptr)
		{
			return ptr;
		}
	%}
%enddef


%define %stream_init_code
	%inline
	%{
		void _py_argot_input_stream_init( type_input_stream * in, type_map * map, PyObject* reader )
		{
			Py_INCREF(reader);
			type_input_init( in, map, _py_argot_read_callback, reader );
		}
	%}
%enddef




%define %insert_write_callback

	%constant int _py_argot_write_callback( type_output_stream * out, void * data, char * buffer, int size );

	%{
		#ifndef _PY_ARGOT_WRITE_CALLBACK_DEFINED
		#define _PY_ARGOT_WRITE_CALLBACK_DEFINED
		
		int _py_argot_write_callback( type_output_stream * out, void * data, char * buffer, int size )
		{
			PyObject* callable, *args, *res, *py_string;
			int result = TYPE_OK;
			
			callable = _validate_callable(data);
			if (!callable) return 0;
	
			py_string = PyString_FromStringAndSize(buffer, size);			
			
			args = Py_BuildValue("(O)", py_string);
			res = PyObject_Call(callable, args, 0);

			Py_DECREF(args);
			Py_DECREF(py_string);
			
			if ( !res )
			{
				print_traceback();
				result = NOTYPE;
			}
			
			/*if (!res || !PyInt_Check(res))
			{
				printf("You have to return an int in your python argot_write callback function!\n");
				result = 0;
			}
			else
			{
				result = PyInt_AsLong(res);
			}*/
			
			Py_XDECREF(res);
			
			return result;
		}
		
		#endif
	%}
	
%enddef


%define %output_stream_code
	%{
		#include "common/BigEndianSignedByte.h"
		#include "common/BigEndianSignedInteger.h"
		#include "common/BigEndianSignedLong.h"
		#include "common/BigEndianSignedShort.h"
		#include "common/BigEndianUnsignedByte.h"
		#include "common/BigEndianUnsignedInteger.h"
		#include "common/BigEndianUnsignedLong.h"
		#include "common/BigEndianUnsignedShort.h"
		#include "common/U8Ascii.h"
		#include "common/U8Boolean.h"
		#include "common/U16ArrayByte.h"
		#include "common/U32ArrayByte.h"
		#include "common/U32UTF8.h"

		void* getFinalObj(type_name* typename, PyObject* obj, void* memory)
		{
			// todo: error checking
		
			char* name = typename->name;
			if ( !strcmp(name, uint8_name.name) )
			{
				*(uint8_t*)memory = (uint8_t)PyInt_AsLong(obj);
			}
			else if ( !strcmp(name, sint8_name.name) )
			{
				*(int8_t*)memory = (int8_t)PyInt_AsLong(obj);
			}
			else if ( !strcmp(name, uint16_name.name) )
			{
				*(uint16_t*)memory = (uint16_t)PyInt_AsLong(obj);
			}
			else if ( !strcmp(name, sint16_name.name) )
			{
				*(int16_t*)memory = (int16_t)PyInt_AsLong(obj);
			}
			else if ( !strcmp(name, uint32_name.name) )
			{
				*(uint32_t*)memory = (uint32_t)PyInt_AsLong(obj);
			}
			else if ( !strcmp(name, sint32_name.name) )
			{
				*(int32_t*)memory = (int32_t)PyInt_AsLong(obj);
			}
			else if ( !strcmp(name, uint64_name.name) )
			{
				*(uint64_t*)memory = (uint64_t)PyInt_AsLong(obj);
			}
			else if ( !strcmp(name, sint64_name.name) )
			{
				*(int64_t*)memory = (int64_t)PyInt_AsLong(obj);
			}
			else if ( !strcmp(name, bool_name.name) )
			{
				*(char*)memory = (obj == Py_False) ? 0 : 1;
			}
			else if ( !strcmp(name, u8ascii_name.name) )
			{
				char* buffer = 0;
				int length = 0;
				
				PyString_AsStringAndSize( obj, &buffer, &length );
				memcpy(memory, buffer, (length + 1) * sizeof(char));
			}
			else if ( !strcmp(name, u32utf8_name.name) )
			{
				printf("u32utf8 not yet implemented!");
			}
			else if ( !strcmp(name, u16binary_name.name) )
			{
				printf("u16binary not yet implemented!");
			}
			else if ( !strcmp(name, u32binary_name.name) )
			{
				printf("u32binary not yet implemented!");
			}
			else
			{
				return obj;
			}
			
			return memory;
		}
	%}

	%inline
	%{
		void _py_argot_output_stream_init( type_output_stream * out, type_map * map, PyObject* writer )
		{
			Py_INCREF(writer);
			type_output_init( out, map, _py_argot_write_callback, writer );
		}

		int _py_argot_output_stream_write_named_object( type_output_stream * out, type_name* name, PyObject* obj )
		{
			// todo: make non-fixed size
			static char memory[65536];
			return type_output_write_named_object( out, name, getFinalObj(name, obj, memory) );
		}

		int _py_argot_output_stream_write_object( type_output_stream * out, int id, PyObject* obj )
		{
			return _py_argot_output_stream_write_named_object( out, type_map_get_name( out->map, id ), obj );
		}

	%}

	%set_constructor(type_output_stream, _py_argot_output_stream_init);
	%add_bound_method( type_output_stream, int, write_object, _py_argot_output_stream_write_object, int systemId, void * object );
	%add_bound_method( type_output_stream, int, write_named_object, _py_argot_output_stream_write_named_object, type_name * name, void * object );
	
%enddef


%define %input_stream_constructor
	%{
		void _py_argot_input_stream_init( type_input_stream * in, type_map * map, PyObject* reader );
	%}
%enddef

%define %set_input_stream_constructor
	%set_constructor_2(type_input_stream, _py_argot_input_stream_init, type_map *, map, PyObject*, reader);
%enddef


%define %type_element_forward_decl
%{
	bool _register_element_bind_func( type_element * element, PyObject* callable );
	void _unregister_element_bind_func( type_element * element );
	int _element_bind_callback( type_element * element, type_definition * definition );

	void _type_element_constructor(type_element* e, PyObject* bindFunc);
	void _type_element_destructor(type_element* e);
%}
%enddef




%define %type_element_bind_callbacks

%set_constructor_1( type_element, _type_element_constructor, PyObject*, bindFunc );
%set_destructor( type_element, _type_element_destructor);

bool _register_element_bind_func( type_element * element, PyObject* callable );
void _unregister_element_bind_func( type_element * element );
%constant int _element_bind_callback( type_element * element, type_definition * definition );
void _type_element_constructor(type_element* e, PyObject* bindFunc);
void _type_element_destructor(type_element* e);

%{
	void _type_element_constructor(type_element* e, PyObject* bindFunc)
	{
		if (!_validate_callable(bindFunc))
		{
			// todo: raise exception?
			printf("The bind parameter must be callable!\n");
			return;
		}
		
		_register_element_bind_func(e, bindFunc);		
		type_element_init(e, _element_bind_callback);
	}

	void _type_element_destructor(type_element* e)
	{
		_unregister_element_bind_func(e);
	}

    bool _register_element_bind_func( type_element * element, PyObject* callable )
    {
		if ( !PyCallable_Check(callable) )
		{
			element->user_data = 0;
			return false;
		}
		
		element->user_data = callable;
		Py_INCREF(callable);
		
		return true;
    }
    
    void _unregister_element_bind_func( type_element * element )
    {
		// todo: 0xdddddddd problem: Py_XDECREF( (PyObject*)(element->user_data) );
    }


	int _element_bind_callback( type_element * element, type_definition * definition )
	{
		PyObject* func, *elem, *def, *args, *res;
		int result;
		
		func = (PyObject*)(element->user_data);
		if (!func)
		{
			printf("No python bind function associated with this element!\n");
			return TYPE_BIND_ERROR;
		}
		
		elem = SWIG_NewPointerObj( element, SWIGTYPE_p_type_element, 0 );
		def = SWIG_NewPointerObj( definition, SWIGTYPE_p_type_definition, 0 );

		args = Py_BuildValue("OO", elem, def);
		res = PyObject_Call(func, args, 0);

		Py_DECREF(args);
		Py_XDECREF(elem);
		Py_XDECREF(def);
		
		result = TYPE_BIND_ERROR;

        if ( !res ) print_traceback();
		
		if (!PyInt_Check(res))
		{
			printf("You have to return an int in your element's bind function!\n");
		}
		else
		{
			result = PyInt_AsLong(res);
		}
		
		Py_XDECREF(res);
		
		return result;
	}
%}

%enddef


%define %type_library_destructor
	%set_destructor( type_library, _py_type_library_destroy );
%enddef

%define %output_stream_forward_decls
%enddef

%include "../../swig/type.i"



%pythoncode
%{
TypeLibrary.register = TypeLibrary.Register
TypeElement.bind = TypeElement.Bind
%}